const fp = require('lodash/fp');
/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/
// 定义状态
const PENDING = 'pending'; // 等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败

class MyPromise {
  static of(fn) {
    return new MyPromise(fn);
  }
  static is(val) {
    return val instanceof MyPromise;
  }
  // 表示状态
  #state = PENDING;
  // 存储成功需要调用的函数
  #successFns = [];
  // 存储失败需要调用的函数
  #errorFns = [];
  // 存回调值
  #value;
  constructor(fn) {
    // 初始化传入resolve成功和reject失败方法，用于接受值和改变状态
    try {
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (err) {
      this.reject(err);
    }
  }
  resolve(val) {
    // 状态不等于等待结束函数
    if (this.#state !== PENDING) return;
    // 存储传入值
    this.#value = val;
    // 改变状态为成功
    this.#state = FULFILLED;
    // 执行所有使用then挂载的成功回调函数
    while (this.#successFns.length) this.#successFns.shift()(val);
  }
  reject(val) {
    // 状态不等于等待结束函数
    if (this.#state !== PENDING) return;
    // 存储传入值
    this.#value = val;
    // 改变状态为失败
    this.#state = REJECTED;
    // 执行所有使用then挂载的失败回调函数
    while (this.#errorFns.length) this.#errorFns.shift()(val);
  }
  // 注册成功回调函数，并返回promise对象
  then(
    successFn = val => val,
    errorFn = val => {
      throw val;
    }
  ) {
    let newPromise = MyPromise.of((resolve, reject) => {
      setTimeout(() => {
        // 柯里化
        const handleReturnVal = fp.curry(MyPromise.handleReturnVal)(newPromise, resolve, reject);
        switch (this.#state) {
          case PENDING:
            // 等待状态存储成功的回调
            try {
              this.#successFns.push(() => handleReturnVal(successFn(this.#value)));
              this.#errorFns.push(() => handleReturnVal(errorFn(this.#value)));
            } catch (err) {
              reject(err);
            }
            break;
          case FULFILLED:
            // 成功状态立即执行回调，并把返回值交给处理返回值函数处理
            try {
              handleReturnVal(successFn(this.#value));
            } catch (err) {
              reject(err);
            }
            break;
          case REJECTED:
            // 失败状态立即执行回调，并把返回值交给处理返回值函数处理
            try {
              handleReturnVal(errorFn(this.#value));
            } catch (err) {
              reject(err);
            }
            break;
          default:
            break;
        }
      }, 0);
    });
    return newPromise;
  }
  // 注册错误回调函数
  catch(fn) {
    this.then(undefined, fn);
  }
  // 处理回调返回值函数
  static handleReturnVal(newPromise, resolve, reject, returnVal) {
    // 判断回调返回值是否与新的promise相同，相同则报错
    if (newPromise === returnVal) return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
    // 判断返回值是否为promise
    if (MyPromise.is(returnVal)) {
      // 调用then传入resolve和reject，当returnVal改变状态时同时改变返回的promise的状态
      returnVal.then(resolve, reject);
    } else {
      // 不是promise则直接改变状态
      resolve(returnVal);
    }
  }
  static all(promises) {
    return MyPromise.of((resolve, reject) => {
      try {
        // 存储成功的值
        const values = [];
        promises.forEach((item, index) => {
          // 判断是否promise对象
          if (MyPromise.is(item)) {
            item.then(
              value => {
                // 按顺序存放值
                values[index] = value;
                // 当值数组长度与传入长度相等即为已经取得所有值，调用resolve改变状态
                values.length === promises.length && resolve(values);
              },
              error => reject(error)
            );
          } else values[index] = item; // 非promise对象直接存储
        });
      } catch (err) {
        reject(err);
      }
    });
  }
  static race(promises) {
    return MyPromise.of((resolve, reject) => {
      try {
        promises.forEach(item => {
          // 最先成功的函数直接调用resolve改变状态
          if (MyPromise.is(item)) item.then(value => resolve(value));
          else resolve(item);
        });
      } catch (err) {
        reject(err);
      }
    });
  }
  static resolve(value) {
    if (MyPromise.is(value)) return value;
    return MyPromise.of(resolve => resolve(value));
  }
}

function p1() {
  return MyPromise.of(function(resolve, reject) {
    setTimeout(function() {
      resolve('p1');
    }, 2000);
  });
}
function p2() {
  return MyPromise.of(function(resolve, reject) {
    reject('失败');
    // resolve('成功');
  });
}

// p1()
//   .then(value => {
//     console.log(value + '1');
//     return p2();
//   })
//   .then(value => {
//     console.log(value + '2');
//     return p1();
//   })
//   .then(value => {
//     console.log(value + '3');
//   });

MyPromise.all([p1(), p2(), p1()]).then(value => console.log(value)).catch(error => console.log(error));

